
use std::cell::RefCell;
use std::mem::size_of;
use std::rc::Rc;

use lua::core::{LuaEngine, ToLuaValue};
use macro_def::PartialReflection;
use vulkan_rhi::{SkinSSBO, Vertex, VulkanRHI, Texture, PerObjectUniformData};

use crate::utils::gltf_loader::{GltfInput, Skin};
use crate::utils::tools::type_distributor;
use crate::{vulkan::vulkan_rhi, utils::constant::MAX_FRAMES_IN_FLIGHT};

// use super::component::component::ObjectComponent;
use super::component::lua_component::LuaComponent;
use super::component::transform_component::TransfromComponent;
use super::object::{DrawCommandData, UniformData, Drawable, Updateable, Object, Componentable, UpdateableContext, ComponentableContext};

use gltf::image::Data as gltfImageData;

#[derive(Clone)]
struct PerObjectAnimationData {
    bind_skin_index: usize,
    joint_stroage_data: SkinSSBO
}
impl PerObjectAnimationData {
    pub fn release_resource(&self, vulkan_rhi: &VulkanRHI) {
        unsafe {
            for buffer in &self.joint_stroage_data.buffer {
                vulkan_rhi.logic_device.destroy_buffer(*buffer, None)
            }
            for device_memory in &self.joint_stroage_data.device_memory {
                vulkan_rhi.logic_device.unmap_memory(*device_memory);
                vulkan_rhi.logic_device.free_memory(*device_memory, None);
            }
        }
    }
}

#[derive(Clone)]
pub struct SkeletalMesh {
    draw_command_data: DrawCommandData,
    model_uniform_data: UniformData,
    //
    bind_gltf_input: Rc<RefCell<GltfInput>>,
    local_inverse_matrix: glm::Matrix4<f32>,
    
    texture: Option<Texture>,
    animation_data: Option<PerObjectAnimationData>,

    // component
    // #[Enable]
    lua_component: LuaComponent,

    // #[Enable]
    transform_component: TransfromComponent
}
impl SkeletalMesh {
    pub fn from_gltf(
        vulkan_rhi: &VulkanRHI, 
        cpu_vertex_buffer: Vec<Vertex>, cpu_index_buffer: Vec<u32>, 
        image: gltfImageData,
        bind_skin: Option<&Skin>,
        bind_skin_index: usize,
        bind_gltf_input: Rc<RefCell<GltfInput>>,
        local_inverse_matrix: glm::Matrix4<f32>) -> Result<Self, Box<dyn std::error::Error>> {
        // 创建Uniform
        let gpu_uniform_buffer = 
            vulkan_rhi.create_uniform_buffers_v2(MAX_FRAMES_IN_FLIGHT, size_of::<PerObjectUniformData>() as u64);
        // 创建Texture
        let texture = 
            vulkan_rhi.create_texture_from_buffer_v2(&image.pixels, image.width, image.height, &vulkan_rhi.graphics_queue);
        // 创建Skin
        let (gpu_storage_buffer, storage_buffer_size) = match bind_skin {
            Some(skin) =>
                (
                    Some(vulkan_rhi.create_skin_buffers_v2(
                        MAX_FRAMES_IN_FLIGHT, (size_of::<[[f32; 4]; 4]>() * skin.inverse_bind_matrices.len()) as u64)),
                    (size_of::<[[f32; 4]; 4]>() * skin.inverse_bind_matrices.len()) as u64
                ),
            None => 
                (None, 0)
        };
        //
        let index_len = cpu_index_buffer.len() as u32;

        let draw_command_data = DrawCommandData {
            vertices: 
                Some(vulkan_rhi.create_buffer_usage_v2::<Vertex>(
                    (cpu_vertex_buffer.len() * size_of::<Vertex>()) as u64, 
                    ash::vk::BufferUsageFlags::VERTEX_BUFFER, 
                    Some(cpu_vertex_buffer))),
            indices:
                Some(vulkan_rhi.create_buffer_usage_v2::<u32>(
                    (cpu_index_buffer.len() * size_of::<u32>()) as u64, 
                    ash::vk::BufferUsageFlags::INDEX_BUFFER, 
                    Some(cpu_index_buffer))),
            descriptor_sets:
                Some(vulkan_rhi.create_descriptor_set_v2(
                    &gpu_uniform_buffer.buffer, &texture.sampler, &texture.image_view, 
                    gpu_storage_buffer.as_ref(), storage_buffer_size)),
            index_len: index_len
        };

        let model_uniform_data = UniformData {
            gpu_buffer: Some(gpu_uniform_buffer),
            model_matrix: cgmath::Matrix4::from_scale(1.0),
            is_animated: 1.0
        };

        let animation_data = match bind_skin {
            Some(_skin) => Some(PerObjectAnimationData {
                bind_skin_index: bind_skin_index,
                joint_stroage_data: gpu_storage_buffer.unwrap()
            }),
            None => None  
        };

        Ok(Self { 
            draw_command_data: draw_command_data, 
            model_uniform_data: model_uniform_data,

            local_inverse_matrix: local_inverse_matrix,
            bind_gltf_input: bind_gltf_input,

            texture: Some(texture),
            animation_data: animation_data,

            lua_component: LuaComponent::default(),
            transform_component: TransfromComponent::default()
        })
    }
}

impl Drawable for SkeletalMesh {
    fn draw(&self, vulkan_rhi: &VulkanRHI, command_buffer_index: usize) {
        self.draw_command_data.draw(vulkan_rhi, command_buffer_index);
    }
}
impl Updateable for SkeletalMesh {
    fn update(&mut self, context: &UpdateableContext) {
        let gltf_input = self.bind_gltf_input.borrow();
        
        self.model_uniform_data.model_matrix = self.transform_component.to_matrix4();

        self.model_uniform_data.update(context);
        
        // 这个Mesh的Skin
        if let Some(animation_data) = &self.animation_data {
            let root_inverse_matrix = self.local_inverse_matrix;

            let skin = gltf_input.skins.get(animation_data.bind_skin_index).unwrap();

            let mut joint_matrices = vec![];
            for joint_index in 0..skin.joints.len() {
                let joint_matrix = 
                    root_inverse_matrix * 
                    gltf_input.get_node_matrix(skin.joints[joint_index].clone()) * 
                    gltf_input.gltf_matrix_to_glm_matrix(skin.inverse_bind_matrices[joint_index]);

                joint_matrices.push(
                    gltf_input.glm_matrix_to_gltf_matrix(joint_matrix.clone()));
            }

            unsafe {
                animation_data.joint_stroage_data.mapped_handle[context.frame_index].copy_from_nonoverlapping(joint_matrices.as_ptr(), joint_matrices.len());
            }
        }
    }
}

impl ToLuaValue for SkeletalMesh {}

impl SkeletalMesh {
    fn get(instance: *const std::ffi::c_void, access_name: &String) -> lua::core::Value {
        let self_instance = type_distributor::<Self>(instance);
        
        if self_instance.is_none() {
            return lua::core::Value::Nil()
        }
        let self_instance = self_instance.unwrap();
        
        match access_name.as_str() {
            "transform_component" => self_instance.transform_component.to_lua_value(),
            _ => lua::core::Value::Nil()
        }
    }
}

impl Componentable for SkeletalMesh {
    fn tick(&mut self, delta_time: f32, context: &ComponentableContext) {
        self.lua_component.tick(delta_time, context);
    }
    fn add_component(&mut self, rc_ref_self: Rc<RefCell<dyn Object>>, context: &mut ComponentableContext) {
        // Init Lua Component
        self.lua_component.post_load(rc_ref_self.clone(), context);
        self.transform_component.post_load(rc_ref_self.clone(), context);
        
        // 创建对应的table
        context.lua_engine.create_object_table(self as *mut Self);
        context.lua_engine.create_metatable("SkeletalMesh");
        context.lua_engine.set_table_cfunction("__index", &Some(LuaEngine::get_distributor), 0);
        context.lua_engine.set_table_cfunction("__newindex", &Some(LuaEngine::set_distributor), 0);
        context.lua_engine.pop_stack(1);
        context.lua_engine.set_metatable("SkeletalMesh", 0);

        context.lua_engine.getter_map_add(String::from("SkeletalMesh"), Self::get);
        
        context.lua_engine.set_global_value("game_object", 0);

        // self.lua_component.run_script(context);
    }
}

impl Object for SkeletalMesh {
    fn release_resource(&mut self, vulkan_rhi: &VulkanRHI) {
        self.draw_command_data.release_resource(vulkan_rhi);
        self.model_uniform_data.release_resource(vulkan_rhi);

        if let Some(texture) = &self.texture { unsafe {
            vulkan_rhi.logic_device.destroy_sampler(texture.sampler, None);
            vulkan_rhi.logic_device.destroy_image_view(texture.image_view, None);
            vulkan_rhi.logic_device.destroy_image(texture.image, None);
            vulkan_rhi.logic_device.free_memory(texture.device_memory, None);   
        }}
        if let Some(animation_data) = &self.animation_data {
            animation_data.release_resource(vulkan_rhi);
        }
    }
}